/* 
核心模块src目录下各个文件夹的作用：
1.compiler：编译用的
  vue使用字符串作为模板，在编译文件中存放对模板字符串解析的算法，抽象语法树，优化等
2.core文件夹，是核心，存在vue构造函数以及生命周期等方法
3.plateforms：针对不同的环境（设备），有不同的实现，也是vue的入口
4.server服务器，主要是将vue用在服务器的处理代码
5.sfc  单文件组件
6.shared：公共的工具、方法
*/

/* cached */
//源码
//这里巧妙运用了闭包缓存了cache的值，如果cache中有需要的数据，就取之前的缓存的值，如果没有就调fn传参得新值
export function cached<F: Function> (fn: F): F {
    const cache = Object.create(null)
    return (function cachedFn (str: string) {
      const hit = cache[str] // 如果已缓存, hit 就是有数据的, 如果为缓存 hit 就是 undefined
      return hit || (cache[str] = fn(str))
    }: any)
  }

  //上述等价代码
  let hit = cached[str]
  if(hit === undefined){
      let res = fn(str)
      cached[str] = res
      return res
  }else{
      return hit
  }


  //makeMap函数， makeMap 生成一个带有缓存的 函数, 用于判断 数据是否是缓存中的数据, 
 //* 代表 判断字符串 ( 标签名 ) 是否为内置的 HTML 标签
  export function makeMap (
    str: string,
    expectsLowerCase?: boolean
  ): (key: string) => true | void {
    const map = Object.create(null)
    const list: Array<string> = str.split(',')
    for (let i = 0; i < list.length; i++) {
      map[list[i]] = true
    }
    return expectsLowerCase
      ? val => map[val.toLowerCase()]
      : val => map[val]
  }


/* looseEqual函数

这个函数主要作用就是来判断两个对象是否相等，判断两个对象是否相等是要判断对象的属性以及属性值的，而在js中是无法直接去判断两个
对象是否相等的，因为对象时引用类型的值，比较的是地址值
该算法的思想是：
如果有两个对象，判断这两个对象是否相等
（1）先遍历a中的成员，判断a中的每一个成员都在b中，并且与b中对应的成员相等
（2）再遍历b中的成员，判断b中的每一个成员都在a中，并且与a中对应的成员相等
（3）如果成员是引用类型的，就递归判断
*/
  export function looseEqual (a: any, b: any): boolean {
    if (a === b) return true
    const isObjectA = isObject(a)
    const isObjectB = isObject(b)
    if (isObjectA && isObjectB) {
      try {
        const isArrayA = Array.isArray(a)
        const isArrayB = Array.isArray(b)
        if (isArrayA && isArrayB) {
          return a.length === b.length && a.every((e, i) => {
            return looseEqual(e, b[i]) // b 包含 a
          })
        } else if (a instanceof Date && b instanceof Date) {
          return a.getTime() === b.getTime() // 单独处理 Date 类型, 时间戳应该是一样的
        } else if ( 0 ) {
          // 如果需要考虑其它类型, 添加 if 即可
        } else if (!isArrayA && !isArrayB) {
          const keysA = Object.keys(a)
          const keysB = Object.keys(b)
          // 先判断 key 的长度, 再判断 a 包含于 b
          return keysA.length === keysB.length && keysA.every(key => {
            return looseEqual(a[key], b[key])
          })
        } else {
          /* istanbul ignore next */
          return false
        }
      } catch (e) {
        /* istanbul ignore next */
        return false
      }
    } else if (!isObjectA && !isObjectB) {
      return String(a) === String(b)
    } else {
      return false
    }
  }


  // 让一个事件 ( 一个函数 ) 只允许调用一次
// 在 vue 中有函数方法 ( on, off 等, once ), once 事件就是这个思路
//这个函数传一个函数参数，返回值也是一个函数，用一个闭包缓存一个变量，来判断该函数是否被调用过，这里也是用到了闭包的思想
/**
 * Ensure a function is called only once.
 */
export function once (fn: Function): Function {
  let called = false // 是否调用过
  return function () {
    if (!called) {
      called = true
      fn.apply(this, arguments)
    }
  }
}


//toString函数
export function toString (val: any): string {
  return val == null
    ? ''
    : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
      ? JSON.stringify(val, null, 2) // 粗略的将引用类型转换为 JSON
      : String(val) // 粗略的将 基本类型 值 转换成 字符串
}



// vue 运行在浏览器中, 所以需要考虑性能
// 每次数据的更新 -> 虚拟 DOM 的生成( 模板解析的行为 ) -> 因此将经常使用的字符串与算法进行缓存
// 在垃圾回收的原则中有一个统计现象: "使用的越多的数据, 一般都会频繁的使用"
// 1. 每次创建一个数据, 我们就会考虑是否要将其回收
// 2. 在数据达到一定限额的时候, 就会考虑将数据回收 ( 回收不是实时 )
//   - 如果每次都有判断对象是否需要回收, 那么就需要遍历
//   - 将对象进行划分, 统计, 往往一个数据使用完以后就必须要使用了.
//   - 一个对象如果在一次回收之后还保留下来, 统计的结果是这个对象会比较持久在内存中驻留.
// 在模板中常常会使用 "指令", 在 vue 中一个 xxx-xxx-xxx 的形式出现的属性 
// 每次数据的更新都可能会带来 指令的 解析, 所以解析就是字符串处理, 一般会消耗一定的性能.


  